/**
 * 
 */
package spitfire.ksim.algorithm;

import java.util.Collections;
import java.util.List;

import TJavaLib.TList;

/**
 * @author Adam
 *
 */
public class RuleExtractionImpl implements RuleExtractionAlgorithm {

	@Override
	public FuzzyRule extractRule(List<Long> timeStampList,
			List<Double> dataList) {
		
//		List<Long> timeStampL = new ArrayList<Long>(timeStampList); 
		// Check data adequacy
//		if (!PreprocAndValidationAlgorithm.procAndValid(timeStampL, dataList)) {
//			System.out.println("Not enough data for Rule Extraction");
//			return null;
//		}

		Double[] raw = dataList.toArray(new Double[dataList.size()]);
		// Identify the value range
		double rawMax = Collections.max(dataList);
		double rawMin = Collections.min(dataList);
		double rawRange = rawMax - rawMin;
		
		// Special case: all values are the same
		if (rawRange <= Double.MIN_VALUE) {
			System.out.println("All values in snapshot are identical");
			double epsilon = 0.05;
			FuzzyRule rule = new FuzzyRule();
			rule.setrMax(rawMax + epsilon);
			rule.setrMin(rawMin - epsilon);
			rule.add(raw[0] - epsilon, 1);
			rule.add(raw[0] + epsilon, 1);
			return rule;
		}
		
		double ruleMax = rawMax + (rawRange + 0.1);
		double ruleMin = rawMin - (rawRange + 0.1);
		double ruleRange = ruleMax-ruleMin;
		
		// Max-Min normalize raw data
		double[] normRaw = new double[raw.length];
		for (int i=0; i<normRaw.length; i++) {
			normRaw[i] = (raw[i] - ruleMin) / ruleRange;
		}
		
		//Discretize the neighborhood density curve
		int dSize = 200;
		double delta = ruleRange / dSize;
		double dndcX[] = new double[dSize];
		double dndcXN[] = new double[dSize];
		double dndcY[] = new double[dSize];
		for (int i=0; i<dSize; i++) {
			dndcX[i] = ruleMin + delta/2 + i*delta;
			dndcXN[i] = (1/(double)dSize)/2 + (double)i/(double)dSize;
			dndcY[i] = 0;
		}
		
		//Calculate neighborhood density curve of data
		double ra = 0.5;
		double alpha = 40/ra/ra;
//		double ndc[] = new double[normRaw.length];
		for (int i=0; i<dndcX.length; i++) {
			dndcY[i] = 0;
			for (int j=0; j<normRaw.length; j++) {
				double d = Math.abs((dndcX[i]-ruleMin)/ruleRange-normRaw[j]);
				dndcY[i] += Math.exp(-alpha*d*d);
			}
		}
		
		// Max-Min normalize neighborhood density
		double ndcMax = Double.MIN_VALUE;
		double ndcMin = Double.MAX_VALUE;
		for (int i=0; i<dndcY.length; i++) {
			if (ndcMax < dndcY[i]) ndcMax = dndcY[i];
			if (ndcMin > dndcY[i]) ndcMin = dndcY[i];
		}
		double ndcRange = ndcMax - ndcMin;
		for (int i=0; i<dndcY.length; i++) {
			dndcY[i] = (dndcY[i] - ndcMin) / ndcRange;
		}
//		System.out.println(Arrays.toString(dndcX));
//		System.out.println(Arrays.toString(dndcXN));
//		System.out.println(Arrays.toString(dndcY));

		/*------ Linearize the discrete neighborhood density curve ------ */
		TList dy = new TList();

		//Eliminate no-data points
		int i1 = 0;
		dy.enList(Double.valueOf(0));
		for (int i2=i1+1; i2<dndcX.length; i2++) {
			double d = (dndcY[i2] - dndcY[i1]) / (dndcXN[i2] - dndcXN[i1]);
			dy.enList(Double.valueOf(d));
			i1 = i2;
		}

		//Linearize the has-data points
		//bugfix: dy can be of size 1, leading to OutOfBoundException
//		if (vx.len() < 2 || vy.len() < 2 || dy.len() < 2) { // temporary solution
//			return null;
//		}
		FuzzyRule rule = new FuzzyRule();
		rule.setrMax(rawMax);
		rule.setrMin(rawMin);
		rule.add(dndcX[0], dndcY[0]);
		if (dy.len() > 1) {
			double thSlope = 1; // PI/40
			double slope1 = ((Double)dy.get(1)).doubleValue();
			for (int i=2; i<dy.len(); i++) {
				double slope2 = ((Double)dy.get(i)).doubleValue();
				double dSlope = Math.abs(slope2-slope1);
				if (dSlope >= thSlope) {
					rule.add(dndcX[i-1], dndcY[i-1]);
					slope1 = slope2;
				}
			}
			rule.add(dndcX[dndcX.length-1], dndcY[dndcY.length-1]);
		}
		return rule;
	}
}
